package com.gitee.service.openapi;

import com.gitee.gateway.core.db.DatabaseHelper;
import com.gitee.gateway.core.utils.AsyncInputStream;
import com.gitee.gateway.core.utils.Resource;
import io.github.resilience4j.core.functions.CheckedRunnable;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.web.RoutingContext;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;

/**
 * 基于函数接口的请求处理器
 */
public interface FunctionHandler extends Handler<RoutingContext>, DatabaseHelper {

    /**
     * !!! IMPORTANT !!!
     * <p>BaseHandler use this callback to know what method to be executed</p>
     *
     * @return action method name to be executed
     */
    Function<RoutingContext, ?> mapping(RoutingContext context);

    /**
     * 核心逻辑：根据 Handler 返回的 Consumer 进行调用并返回结果
     */
    @Override
    default void handle(RoutingContext context) {
        try {
            Function<RoutingContext, ?> func = mapping(context);
            if (func == null) {
                context.fail(HttpResponseStatus.NOT_FOUND.code());
                return;
            }
            //call the handler method
            var result = func.apply(context);
            switch (result) {
                case null -> context.json(null);
                case Redirect redirect -> context.redirect(redirect.url());
                case AsyncInputStream stream -> {
                    stream.handler(context.response()::write);
                    stream.endHandler(v -> {
                        stream.close();
                        context.response().end();
                    });
                }
                case String str -> context.end(str);
                case Void v -> context.end();
                case Future<?> future -> future.onSuccess(r -> {
                    if (r instanceof Buffer buffer)
                        context.end(buffer);
                    else if (r instanceof String str)
                        context.end(str);
                    else if (r instanceof AsyncInputStream stream) {
                        stream.handler(context.response()::write);
                        stream.endHandler(v -> {
                            stream.close();
                            context.response().end();
                        });
                    } else
                        context.json(r);
                }).onFailure(context::fail);
                default -> context.json(result);
            }
        } catch (Exception e) {
            context.fail(e);
        }
    }

    default <R> Future<R> checkLimiterAndCall(RateLimiterRegistry registry, String key, Function<String, Future<R>> func) {
        RateLimiter rateLimiter = registry.rateLimiter(key);
        List<Future<R>> futures = new ArrayList<>();
        CheckedRunnable restrictedCall = RateLimiter.decorateCheckedRunnable(rateLimiter, () -> futures.add(func.apply(key)));
        try {
            restrictedCall.run();
            return futures.remove(0);
        } catch (Throwable t) {
            return Future.failedFuture(t);
        }
    }


    default Future failWithResource(RoutingContext context, HttpResponseStatus status, String key, Object... args) {
        String errorMsg = resource(context, key, args);
        return Future.failedFuture(new OperationException(status, errorMsg));
    }

    default Future failWithMessage(HttpResponseStatus status, String message, Object... args) {
        String newMsg = String.format(message, args);
        return Future.failedFuture(new OperationException(status, newMsg));
    }

    default void throwExceptionWithResource(RoutingContext context, HttpResponseStatus status, String key, Object... args) {
        String errorMsg = resource(context, key, args);
        throw new OperationException(status, errorMsg);
    }

    default String resource(RoutingContext context, String key, Object... args) {
        return Resource.get(context, key, args);
    }

    default String resource(Locale locale, String key, Object... args) {
        return Resource.get(locale, key, args);
    }

}
